/**
 * 
 */
package logic.trie;

import io.ImageReader;

import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import datamodel.RGB;
import datamodel.trie.Trie;
import datamodel.trie.TrieNode;

/**
 * provides methods to build and maintain a TrieCode- Tree
 * 
 * @author Jakob Karolus, Kevin Munk
 * @version 1.0
 * 
 */
public class TrieCode {

	private Trie trieCodeTree;

	/**
	 * constructor
	 * 
	 */
	public TrieCode() {
		this.trieCodeTree = new Trie();
	}

	/**
	 * builds a trie from the given image
	 * 
	 * @param reader the image via an ImageReader
	 */
	public void buildTrie(ImageReader reader) {
		for (RGB rgb : reader) {
			addColor(rgb);
		}
	}

	/**
	 * encrypts the given picture (via an ImageReader) using the stored trie
	 * 
	 * @param reader the picture via an ImageReader
	 * @return the encrypted picture as a binary string
	 */
	public String encryptImage(ImageReader reader) {
		StringBuffer encryptedCode = new StringBuffer();
		for (RGB rgb : reader) {
			List<String> path = trieCodeTree.colorToKey(rgb);
			encryptedCode.append(traverse(path));
		}
		return encryptedCode.toString();
	}

	/**
	 * adds the color to this trie<br>
	 * 
	 * @param color the color to add
	 */
	public void addColor(RGB color) {
		List<String> Farbe = trieCodeTree.colorToKey(color);
		TrieNode node = trieCodeTree.getRoot();
		for (int level = 0; level < Farbe.size()-1; level++) {
			String pack = Farbe.get(level);

			node.setNode(pack);
			node = node.getNode(pack);
		}
		node.setValue(Farbe.get(Farbe.size()-1));
	}

	/**
	 * decrypts the given picture and returns it via a BufferedImage
	 * 
	 * @param picture the picture encrypted as a binary string
	 * @param height the height of the picture
	 * @param width the width of the picture
	 * @return BufferedImage containing the picture
	 */
	public BufferedImage decryptImage(String picture, int height, int width) {

		// Ausgabe-Bild
		BufferedImage image = new BufferedImage(width, height,
				BufferedImage.TYPE_INT_ARGB);

		// Koordinaten zum erstellen des Bildes
		int x = 0;
		int y = 0;

		// Die einzelnen Pixel aus den binaryString des Bildes entnehmen
		List<String> pixel = new ArrayList<String>();
		for (int level = 0; level < picture.length(); level = level + 32) {
			pixel.add(picture.substring(level, level + 32));
		}

		// Einzelne Farbe soll ins Bild eingefügt werden
		for (String singlePixel : pixel) {
			List<String> path = splitIntoPacks(singlePixel);
			StringBuffer buildColor = new StringBuffer();
			buildColor.append(traverse(path));

			if (x < width) {
				image.setRGB(x, y, keyToColor(buildColor.toString()).getRGBValue());
			} else if (y < height) {
				y++;
				x = 0;
				image.setRGB(x, y, keyToColor(buildColor.toString()).getRGBValue());
			}
			x++;
		}
		return image;
	}

	/**
	 * Durchläuft den Baum durch den gegebenen Pfad. Wenn der Pfad nicht
	 * existiert, werden "1000"-Tupel angehängt
	 * 
	 * @param path der zu durchlaufende Pfad
	 * @return Der Pfad soweit wie möglich und erweitert mit "1000" wenn Pfad
	 *         nicht ganz vorhanden ist.
	 */
	private String traverse(List<String> path) {
		TrieNode node = trieCodeTree.getRoot();
		int counter = 0;
		String existingPath = "";
		for (String code : path) {
			if (node != null && node.hasValue(code)) {
				counter++;
				existingPath = existingPath + code;
				node = node.getNode(code);
			} else {
				for (; counter < 8; counter++) {
					existingPath = existingPath + "1000";
				}
			}
		}
		return existingPath;
	}

	/**
	 * Wandelt die binär-codierte Farbe (die vierer-Tupel) in ein RGB um.
	 * 
	 * @param color Die Farbe in vierer Tupel codiert.
	 * @return Gibt die Farbe wieder
	 */
	private RGB keyToColor(String color) {
		String brightness = "" + color.charAt(0) + color.charAt(4)
				+ color.charAt(8) + color.charAt(12) + color.charAt(16)
				+ color.charAt(20) + color.charAt(24) + color.charAt(28);

		String red = "" + color.charAt(1) + color.charAt(5) + color.charAt(9)
				+ color.charAt(13) + color.charAt(17) + color.charAt(21)
				+ color.charAt(25) + color.charAt(29);

		String green = "" + color.charAt(2) + color.charAt(6)
				+ color.charAt(10) + color.charAt(14) + color.charAt(18)
				+ color.charAt(22) + color.charAt(26) + color.charAt(30);

		String blue = "" + color.charAt(3) + color.charAt(7) + color.charAt(11)
				+ color.charAt(15) + color.charAt(19) + color.charAt(23)
				+ color.charAt(27) + color.charAt(31);

		return new RGB(Integer.parseInt(red, 2), Integer.parseInt(green, 2),
				Integer.parseInt(blue, 2), Integer.parseInt(brightness, 2));
	}

	/**
	 * Wandelt den String in vierer-Packs um und speichert diese in einer Liste
	 * 
	 * @param code Das Pixel im 32-Bitcode
	 * @return Eine 8 elementige Liste mit vierer-Tupel
	 */
	private List<String> splitIntoPacks(String code) {
		List<String> info = new ArrayList<String>();
		for (int level = 0; level < code.length(); level = level + 4) {
			info.add(code.substring(level, level + 4));
		}
		return info;
	}

	/**
	 * severs the given amount of levels in the stored tree to compress image
	 * information<br>
	 * if the amount is bigger than the actual number of levels nothing is done
	 * 
	 * @param count the amount of levels to sever
	 * @return true if severing was successful; otherwise false (count >= number
	 *         of levels)
	 */
	public boolean compress(int count) {
		int level = getLevel(trieCodeTree.getRoot(), 0);
		if (count >= level)
			return false;

		level = level - count;
		cutting(trieCodeTree.getRoot(), level, 1);
		return true;

	}

	/**
	 * Bestimmt die Anzahl an Ebenen im Baum
	 * @param node "Wurzelknoten", vom dem aus die Höhe bestimmt werden soll
	 * @param acc Akkumulator (beim Aufruf 0)
	 * @return Die Anzahl der Ebenen im Baum
	 */
	public int getLevel(TrieNode node, int acc) {
		if(node == null)
			return acc;
		
		acc++;
		return  Math.max(getLevel(node.getNode(0), acc), 
				Math.max(getLevel(node.getNode(1), acc),
				Math.max(getLevel(node.getNode(2), acc),
				Math.max(getLevel(node.getNode(3), acc),
				Math.max(getLevel(node.getNode(4), acc),
				Math.max(getLevel(node.getNode(5), acc),
				Math.max(getLevel(node.getNode(6), acc),
				Math.max(getLevel(node.getNode(7), acc),
				Math.max(getLevel(node.getNode(8), acc),
				Math.max(getLevel(node.getNode(9), acc),
				Math.max(getLevel(node.getNode(10), acc),
				Math.max(getLevel(node.getNode(11), acc),
				Math.max(getLevel(node.getNode(12), acc), 
				Math.max(getLevel(node.getNode(13), acc),
				Math.max(getLevel(node.getNode(14), acc), 
						getLevel(node.getNode(15), acc))))))))))))))));
	}

	/**
	 * Entfernt Ebenen sodass das Level des Baums mit maxLevel übereinstimmt
	 * 
	 * @param node "Wurzelknoten" von dem aus der Baum gekürzt werden soll
	 * @param maxLevel Die maximale Anzahl an Levels die der Baum haben darf
	 * @param akk Akkumulator (beim Aufruf 1)
	 */
	private void cutting(TrieNode node, int maxLevel, int acc) {
		if (acc == maxLevel) {
			node.clearNext();
		} else {
			for (int i = 0; i < 16; i++) {
				if (node.getNode(i) != null) {
					cutting(node.getNode(i), maxLevel, acc + 1);
				}
			}
		}
	}

	/**
	 * return true if the given color is in the trie
	 * 
	 * @param color the color to search
	 * @return true if tree contains color; otherwise false
	 */
	public boolean containsColor(RGB color) {
		List<String> path = trieCodeTree.colorToKey(color);
		TrieNode node = trieCodeTree.getRoot();
		for (String code : path) {
			if (!node.hasValue(code)) {
				return false;
			}
			node = node.getNode(code);
		}
		return true;
	}

	/**
	 * @return the trieCodeTree
	 */
	public Trie getTrieCodeTree() {
		return trieCodeTree;
	}

}
